/**
 *  TextMash - simple IDE for Clojure
 *  
 *  Copyright (C) 2010 Aleksander Naszko
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package pl.olek.clojure;

import java.io.DataInputStream;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.util.Map;
import java.util.TreeMap;

import pl.olek.jruce.GroupReceiver;
import pl.olek.textmash.Support;
import pl.olek.textmash.event.EventDispatcher;

/**
 * 
 * @author anaszko
 *
 */
public class RemoteManager {

	private static RemoteManager singleton;

	GroupReceiver gr;
	int tries;
	long waitOnFailure;
	TreeMap<String, InetSocketAddress> remotes = new TreeMap<String, InetSocketAddress>();

	private RemoteManager() {
		Configuration cfg = new Configuration(Support.getInstance().getId());
		tries = cfg.getNumber("tries").intValue();
		waitOnFailure = cfg.getNumber("wait-on-failure").longValue();
		gr = new GroupReceiver("textmash") {

			@Override
			public void receive(InetSocketAddress incomingAddress,
					InputStream input) throws Exception {
				DataInputStream din = new DataInputStream(input);
				String name = din.readUTF();
				int port = din.readInt();
				boolean changed = false;
				synchronized (remotes) {
					changed = (remotes.put(name, new InetSocketAddress(
							incomingAddress.getHostName(), port)) == null);
					remotes.notify();
				}
				if (changed) {
					EventDispatcher.getInstance().fire(null,
							EventDispatcher.REMOTE_CHANGED);
				}
			}
		};
	}

	public Map<String, String> list() {
		TreeMap<String, String> result = new TreeMap<String, String>();
		synchronized (remotes) {
			for (Map.Entry<String, InetSocketAddress> v : remotes.entrySet()) {
				InetSocketAddress addr = v.getValue();
				result.put(v.getKey(), v.getKey() + " (" + addr.getHostName()
						+ ":" + addr.getPort() + ")");
			}
		}
		return result;
	}

	private InetSocketAddress getAddress(String name) {
		try {
			synchronized (remotes) {
				for (int i = 0; i < tries; ++i) {
					InetSocketAddress addr = remotes.get(name);
					if (addr != null) {
						return addr;
					}
					remotes.wait(waitOnFailure);
				}
			}
			return null;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public Remote access(String name) {
		if (name == null) {
			return null;
		}
		try {

			for (int i = 0; i < tries; ++i) {
				try {
					InetSocketAddress address = getAddress(name);
					if (address != null) {
						return new Remote(address.getHostName(), address
								.getPort());
					} else {
						Thread.sleep(waitOnFailure);
					}
				} catch (Exception e) {
					if (i == 3) {
//						break;
						
						synchronized (remotes) {
							remotes.remove(name);
							remotes.notify();
						}
						EventDispatcher.getInstance().fire(null,
								EventDispatcher.REMOTE_CHANGED);
						
						throw e;
					}
					
					Thread.sleep(waitOnFailure);
				}
			}


			return null;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public synchronized static RemoteManager getInstance() {
		if (singleton == null) {
			singleton = new RemoteManager();
		}
		return singleton;
	}

}
